/*
 
Enterprise Social Network (ESN) is an open-source Java EE framework for
a social network application.

Copyright (C) 2006-07 (see CONTRIBUTORS file in project root)

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

*/

package esn.core.ejb.session;

import javax.annotation.*;
import javax.annotation.security.*;
import javax.ejb.*;
import javax.persistence.*;
import javax.interceptor.*;
import javax.jws.*;
import java.util.*;

import org.jboss.annotation.security.*;

import esn.core.ejb.interceptor.*;
import esn.core.ejb.entity.*;
import esn.core.ejb.exception.*;
import esn.core.ejb.ws.*;
import esn.core.ejb.ws.wrapper.*;

@Stateless
@WebService(endpointInterface="esn.core.ejb.ws.ESNProfileManagerEndpoint")
@SecurityDomain("esn-core") //!JBoss-specific annotation!
@RunAs(UserRole.ROLE__ESN_EJB) //when this EJB calls another one this role will be used
@RolesAllowed({UserRole.ROLE__ESN_USER,
	UserRole.ROLE__ESN_ADMIN,
	UserRole.ROLE__ESN_EJB})
public class ESNProfileManager implements ESNProfileManagerLocal, ESNProfileManagerRemote, ESNProfileManagerEndpoint{
	@PersistenceContext(unitName="esn-core") private EntityManager manager;
	@Resource SessionContext ctx;
	
	@PermitAll
	@Interceptors({EJBExceptionLogger.class})
	public void createProfile(UserProfile newProfile) {
		/*
		 * TODO: Business logic for data consistency check, such as password length?
		 * May be use some sort of rule-based validation defined through XML?
		 */
		manager.persist(newProfile);
	}
	
	@Interceptors({EJBExceptionLogger.class})
	public void changeProfilePassword(String username, String newPassword) {
		//TODO: implement password length restriction at this point?
		if(ctx.getCallerPrincipal().getName().compareTo(username)==0
				|| ctx.isCallerInRole(UserRole.ROLE__ESN_ADMIN)) {
			//this user is allowed to change this profile password
			//find the requested profile
			Query findQuery = manager.createQuery(
					"SELECT OBJECT(o) FROM UserProfile AS o WHERE o.authentication.userId=?1");
			findQuery.setParameter(1,username);
			UserProfile found = (UserProfile)findQuery.getSingleResult();
			found.getAuthentication().digestPassword(newPassword);
			manager.flush();
		}
		else {
			throw new AuthorisationException(
					"Caller '" + ctx.getCallerPrincipal().getName() + 
					"' is not authorised to change password for '" +
					username + "'");
		}
	}
	
	@Interceptors({EJBExceptionLogger.class})
	public Collection<UserProfile> getFriends(long userId) {
		ArrayList<UserProfile> friends = new ArrayList<UserProfile>();
		UserProfile found = manager.find(UserProfile.class,userId);
		//either traverse egress or ingress edges
		Collection<ESNEdge> links = found.getEgressEdges();
		for(ESNEdge edge : links) {
			/*
			 * TODO: Is there any need to erase 'sensitive' authentication information on
			 * the friend without persisting the change to the database before adding
			 * the friend to the collection? Note that passwords are one-way hashed.
			 * 
			 * A front-end (including a Web service end-point) can do cleaning up if
			 * necessary. This applies to some other similar methods.
			 * 
			 * In addition, how do we get the trust values? Does it look like we should
			 * be having a specialised data object instead of returning the UserProfile
			 * entity?
			 */
			friends.add(edge.getDestination());
		}
		return friends;
	}
	
	@Interceptors({EJBExceptionLogger.class})
	public UserProfile getUserProfile(long userId) {
		//find the requested profile
		return manager.find(UserProfile.class,userId);
	}
	
	@Interceptors({EJBExceptionLogger.class})
	public void changeRelationshipTrust(long userId, long friendId, double trustValue) {
		UserProfile found = manager.find(UserProfile.class,userId);
		if(found.getAuthentication().getUserId()!=ctx.getCallerPrincipal().getName()) {
			throw new AuthorisationException(
					"Caller '" + ctx.getCallerPrincipal().getName() + 
					"' is not authorised to change trust value on any egress edge for '" +
					found.getAuthentication().getUserId() + "'");
		}
		//find the egress edge (if exists)
		Query findQuery = manager.createQuery(
			"SELECT OBJECT(o) FROM ESNEdge AS o WHERE o.source.id=?1 AND o.destination.id=?2");
		findQuery.setParameter(1,userId);
		findQuery.setParameter(2,friendId);
		ESNEdge edge = (ESNEdge)findQuery.getSingleResult(); //will fail if edge is non-existent
		edge.setTrust(trustValue);
		manager.flush(); //flush the change before triggering change event
		/*
		 * TODO: Trigger an event (use JMS and Message Beans) so that the change is
		 * considered for re-computation of global trust value of the destination profile
		 * of the egress edge.
		 * 
		 * Strategy for calculating global trust value:
		 * 1. Sum the trust value on each ingress edge weighted by the global trust of
		 * the source node (UserProfile) and divide it by the number of ingress edges.
		 * 2. Use method 1 but pick only a fixed window size instead of all ingress edges.
		 * In that case, which ingress edges are to be considered? The active ones? How do
		 * we find active edges?
		 * 3. Same as method 2 but window size is logarithmically proportional to the total
		 * number of ingress edges.
		 * 4. A better statistical measure than weighted average?
		 */
	}
	

/* ======== WEB SERVICE METHODS ========*/
	//FIXME: To be coded!
	/*
	 * TODO: Determine use of document/literal binding, which imposes
	 * restrictions on parameters and results. Use the idea of 
	 * status messages as return values from web service methods; or
	 * declare them one-way, so that they can be called asynchronously.
	 */
	@PermitAll
	public void createProfileWS(UserProfileWS newProfile) {
		UserProfile temp = new UserProfile();
		//do all the newProfile to temp conversion
		createProfile(temp);
	}
}
